//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

///////////////////////////////////////////////////////////////////
//
// NOTE: PLEASE READ THE README.TXT FILE FOR INSTRUCTIONS ON
// COMPILING AND USAGE REQUIREMENTS.
//
// DESCRIPTION: NV20-specific (Geforce3) sample shader.
//				This shader is meant to produce a anisotropic shading effect.
//				It allows the user to change the parameters of a anisotropic
//				lookup table.
//
//  This shader builds on the foundation demonstrated in the 
//  hwUnlitShader.
//
//	PS: Thanks go to DAR from nVidia, for his help in making this
//      shader more robust. ;-)
//
///////////////////////////////////////////////////////////////////

#ifdef WIN32
#pragma warning( disable : 4786 )		// Disable STL warnings.
#endif

#include <maya/MIOStream.h>
#include <math.h>

#include <maya/MString.h>
#include <maya/MPlug.h>
#include <maya/MDagPath.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MArrayDataHandle.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnLightDataAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MFnStringData.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MSceneMessage.h>

#include <maya/MPoint.h>
#include <maya/MMatrix.h>
#include <maya/MVector.h>
#include <maya/MEulerRotation.h>
#include <maya/MFnLight.h>
#include <maya/MFnNonAmbientLight.h>

// Include NVIDIA's helper libraries.  These libraries have
// copyright info in them so we cannot release them but we
// can use them to verify that the API works correctly.
//
#include <GL/gl.h>
#include <GL/glu.h>
#include <gl/glext.h>

#define GLH_EXT_SINGLE_FILE
#include "glh_extensions.h"
#undef GL_NV_vertex_array_range
#include "glh_genext.h"
#include "glh_obs.h"
using namespace glh;

#include "hwAnisotropicShader_NV20.h"
#include "ShadingConnection.h"

#define TANGENT_INDEX 1 // macro to make normall array indexing a bit clearer

MTypeId hwAnisotropicShader_NV20::id( 0x00105444 );

// Lookup table. Fixed at 256x256 resolution.
/*static*/ const unsigned int hwAnisotropicShader_NV20::lookup_texture_size(256);

void hwAnisotropicShader_NV20::postConstructor( )
{
	setMPSafe(false);
}

// Static attribute instances.
//
MObject  hwAnisotropicShader_NV20::color;
MObject  hwAnisotropicShader_NV20::colorR;
MObject  hwAnisotropicShader_NV20::colorG;
MObject  hwAnisotropicShader_NV20::colorB;

MObject  hwAnisotropicShader_NV20::roughness;
MObject  hwAnisotropicShader_NV20::kDiffuse;
MObject  hwAnisotropicShader_NV20::kSpecular;

void hwAnisotropicShader_NV20::printGlError( const char *call )
{
    GLenum error;

	while( (error = glGetError()) != GL_NO_ERROR ) {
		assert(0);
	    cerr << call << ":" << error << " is " << (const char *)gluErrorString( error ) << "\n";
	}
}


// The Vertex Program for the model shading effect.
// Meant to be used as an anisotropic shader.
//
// Version for point lights with a decay factor
//
// CONSTANTS: (c)
// c0- c3 4x4 ModelView-Projection composite matrix
// c4	  (.5, 1, 0, 0) vector for compute u,v
// c9	  light color
// c11     light position (in object space)
// c12	  camera position in object space (possibly not normalized)

// VERTEX REGISTERS (mapped so that standard gl calls work):
// v0 - position
// v2 - tangent
// v3 - primary color

// REGISTERS:
//
// R0 - normalized view (surface-to-camera) direction in object space.
// R1 - normalized surface tangent in object space.
// R2 - normalized light (surface-to-light) direction.
// R3 - temporary computation of u,v values for lookup-texture to be
//		placed int TEX[0]
// R4,R5 - temporaries use to compute light decay
//
char vertexProgramStringPointDecay[] = 
	"!!VP1.0\n"

		// Multiply the vertex coords by the modelview-projection composite matrix,
		// to get clip space coordinates.
		"DP4   o[HPOS].x, c[0], v[0];"
		"DP4   o[HPOS].y, c[1], v[0];"
		"DP4   o[HPOS].z, c[2], v[0];"
		"DP4   o[HPOS].w, c[3], v[0];"

		// Normalize the T (tangent), in case the modelview matrix is not a simple rotation.
		"MOV   R1, v[2];"
		"DP3   R1.w, R1, R1;"			
		"RSQ   R1.w, R1.w;"
		"MUL   R1.xyz, R1, R1.w;"

		// Compute the normalized light vector in object space
		"ADD   R2, c[11], -v[0];"
		"DP3   R2.w, R2, R2;"			
		"RSQ   R2.w, R2.w;"
		"MUL   R2.xyz, R2, R2.w;"

		// The vertex position, normal and light positions are expressed in object space at
		// this point. We need to find the view direction in object space too.
		"ADD   R0, c[12], -v[0];"		// view direction, from surface to camera.
		"DP3   R0.w, R0, R0;"			// normalize the view direction.
		"RSQ   R0.w, R0.w;"
		"MUL   R0.xyz, R0, R0.w;"
		
		// Find the texture coordinates to fetch from the toon-param texture:
		//		u = 1/2 (T dot L) + 1/2
		//		v = 1/2 (T dot V) + 1/2
		//
		// a) Do (T dot L) and (T dot V) and store in register R3
		"DP3	R3.x, R1, R2;"
		"DP3	R3.y, R1, R0;"

		// b) Scale by 1/2 and add 1/2 and store as texture coordinates
		//
		"MAD o[TEX0].x, R3.x, c[4].x, c[4].x;"
		"MAD o[TEX0].y, R3.y, c[4].x, c[4].x;"

		// Multiply diffuse lighting into material color.
		// Take into account decay
		"MOV R4, c[9];"
		"MOV R5, c[9];"
		"MUL R5.xyz, R4, R2.w;"
		"MUL o[COL0], v[3], R5;"

        "END";

// Version for point lights
//
// CONSTANTS: (c)
// c0- c3 4x4 ModelView-Projection composite matrix
// c4	  (.5, 1, 0, 0) vector for compute u,v
// c9	  light color
// c11     light position (in object space)
// c12	  camera position in object space (possibly not normalized)

// VERTEX REGISTERS (mapped so that standard gl calls work):
// v0 - position
// v2 - tangent
// v3 - primary color

// REGISTERS:
//
// R0 - normalized view (surface-to-camera) direction in object space.
// R1 - normalized surface tangent in object space.
// R2 - normalized light (surface-to-light) direction.
// R3 - temporary computation of u,v values for lookup-texture to be
//		placed int TEX[0]
//
char vertexProgramStringPoint[] = 
	"!!VP1.0\n"

		// Multiply the vertex coords by the modelview-projection composite matrix,
		// to get clip space coordinates.
		"DP4   o[HPOS].x, c[0], v[0];"
		"DP4   o[HPOS].y, c[1], v[0];"
		"DP4   o[HPOS].z, c[2], v[0];"
		"DP4   o[HPOS].w, c[3], v[0];"

		// Normalize the T (tangent), in case the modelview matrix is not a simple rotation.
		"MOV   R1, v[2];"
		"DP3   R1.w, R1, R1;"			
		"RSQ   R1.w, R1.w;"
		"MUL   R1.xyz, R1, R1.w;"

		// Compute the normalized light vector in object space
		"ADD   R2, c[11], -v[0];"
		"DP3   R2.w, R2, R2;"			
		"RSQ   R2.w, R2.w;"
		"MUL   R2.xyz, R2, R2.w;"

		// The vertex position, normal and light positions are expressed in object space at
		// this point. We need to find the view direction in object space too.
		"ADD   R0, c[12], -v[0];"		// view direction, from surface to camera.
		"DP3   R0.w, R0, R0;"			// normalize the view direction.
		"RSQ   R0.w, R0.w;"
		"MUL   R0.xyz, R0, R0.w;"
		
		// Find the texture coordinates to fetch from the toon-param texture:
		//		u = 1/2 (T dot L) + 1/2
		//		v = 1/2 (T dot V) + 1/2
		//
		// a) Do (T dot L) and (T dot V) and store in register R3
		"DP3	R3.x, R1, R2;"
		"DP3	R3.y, R1, R0;"

		// b) Scale by 1/2 and add 1/2 and store as texture coordinates
		//
		"MAD o[TEX0].x, R3.x, c[4].x, c[4].x;"
		"MAD o[TEX0].y, R3.y, c[4].x, c[4].x;"

		// Multiply diffuse lighting into material color.
		"MUL o[COL0], v[3], c[9];"

        "END";

// Version for directional lights
//
// CONSTANTS: (c)
// c0- c3 4x4 ModelView-Projection composite matrix
// c4	  (.5, 1, 0, 0) vector for compute u,v
// c9	  light color
// c11    light direction (in object space)
// c12	  camera position in object space (possibly not normalized)

// VERTEX REGISTERS (mapped so that standard gl calls work):
// v0 - position
// v2 - tangent
// v3 - primary color

// REGISTERS:
//
// R0 - normalized view (surface-to-camera) direction in object space.
// R1 - normalized surface tangent in object space.
// R2 - normalized light (surface-to-light) direction.
// R3 - temporary computation of u,v values for lookup-texture to be
//		placed int TEX[0]
//
char vertexProgramString[] = 
	"!!VP1.0\n"

		// Multiply the vertex coords by the modelview-projection composite matrix,
		// to get clip space coordinates.
		"DP4   o[HPOS].x, c[0], v[0];"
		"DP4   o[HPOS].y, c[1], v[0];"
		"DP4   o[HPOS].z, c[2], v[0];"
		"DP4   o[HPOS].w, c[3], v[0];"

		// Normalize the T (tangent), in case the modelview matrix is not a simple rotation.
		"MOV   R1, v[2];"
		"DP3   R1.w, R1, R1;"			
		"RSQ   R1.w, R1.w;"
		"MUL   R1.xyz, R1, R1.w;"

		// Normalize the L (light) direction.
		"MOV   R2, c[11];"
		"DP3   R2.w, R2, R2;"			
		"RSQ   R2.w, R2.w;"
		"MUL   R2.xyz, R2, R2.w;"

		// The vertex position, normal and light positions are expressed in object space at
		// this point. We need to find the view direction in object space too.
		"ADD   R0, c[12], -v[0];"		// view direction, from surface to camera.
		"DP3   R0.w, R0, R0;"			// normalize the view direction.
		"RSQ   R0.w, R0.w;"
		"MUL   R0.xyz, R0, R0.w;"
		
		// Find the texture coordinates to fetch from the toon-param texture:
		//		u = 1/2 (N dot L) + 1/2
		//		v = 1/2 (T dot V) + 1/2
		//
		// a) Do (T dot L) and (T dot V) and store in register R3
		"DP3	R3.x, R1, R2;"
		"DP3	R3.y, R1, R0;"

		// b) Scale by 1/2 and add 1/2 and store as texture coordinates
		//
		"MAD o[TEX0].x, R3.x, c[4].x, c[4].x;"
		"MAD o[TEX0].y, R3.y, c[4].x, c[4].x;"

		// Multiply diffuse lighting into material color.
		"MUL o[COL0], v[3], c[9];"

        "END";



void initVertexProgram(const char vertexProgramCode[], GLuint* pVertexProgramId)
{
	// Allocate and initialize the vertex program.
	glGenProgramsNV(1, pVertexProgramId);
	GLenum error = glGetError();
	assert(error == GL_NO_ERROR);

	// Load the program.
	unsigned int length = strlen(vertexProgramCode);
	glLoadProgramNV(GL_VERTEX_PROGRAM_NV, *pVertexProgramId, length, 
		(const GLubyte *) vertexProgramCode);
	error = glGetError();

	// If an error occured, find the location in the vertex program
	// code and assert.
	if (error != GL_NO_ERROR)
	{
		// If an error occured, it's most likely due to a syntax or 
		// logic error in the vertex program. The error position
		// below will contain the index in the vertex program
		// string that is faulty. See the NV_vertex_program
		// extension specification for more details.
		if (error == GL_INVALID_OPERATION)
		{
			int error_position = -2;

			glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &error_position);

			// Most likely a bug in the vertex program code...
			assert(0);
		}
	}
}


// Load the vertexProgram and fill in the necessary constants used in the vertex program.
//
void hwAnisotropicShader_NV20::loadVertexProgramGL()
{
	GLenum error = glGetError();
	assert(!error);

	// If the vertex programs haven't been loaded yet,
	// do it now. (Note that they are shared between all contexts.)
	if (!fVertexProgramsLoaded)
	{
		initVertexProgram(vertexProgramString, &fVertexProgramDirectional);
		initVertexProgram(vertexProgramStringPointDecay, &fVertexProgramPointDecay);
		initVertexProgram(vertexProgramStringPoint, &fVertexProgramPointNoDecay);
		fVertexProgramsLoaded = true;
	}
}

// Anisotropic material lookup texture
// (This function assumes that lookup_image and lookup_texture have been deallocated.)
//
void hwAnisotropicShader_NV20::make_lookup_texture()
{
	// Re-calculate the look-up texture, if any of the material paramters
	// have changed
	//
	float	t_roughness = 0.025f;
	float	t_kd = 0.8f;
	float	t_ks = 0.2f;

	//MPlug	colorPlug(thisMObject(), color);
	MPlug	roughPlug(thisMObject(), roughness);
	MPlug	diffPlug(thisMObject(), kDiffuse);
	MPlug	specPlug(thisMObject(), kSpecular);
	
	roughPlug.getValue(t_roughness);
	diffPlug.getValue(t_kd);
	specPlug.getValue(t_ks);

	if ( t_roughness < 0.0f ) t_roughness = 0.0f;
	if ( t_roughness > 1.0f ) t_roughness = 1.0f;
	if ( t_kd < 0.000000000001f ) t_kd = 0.000000000001f; // To prevent underflow in computation
	if ( t_kd> 1.0f ) t_kd= 1.0f;
	if ( t_ks < 0.0f ) t_ks= 0.0f;
	if ( t_ks > 1.0f ) t_ks = 1.0f;

	// Only recompute the lookup texture if the values have changed since the last bind.
	boolean dirty = false;
	if	(currentKd != t_kd) {
		dirty = true; currentKd = t_kd;
	}
	if (currentKs != t_ks) {
		dirty = true; currentKs = t_ks; 
	}
	if (currentRoughness != t_roughness) {
		dirty = true; currentRoughness = t_roughness;		
	}
	if (!dirty) 
		return;
	
	unsigned int imgsize = lookup_texture_size;
	float imgsizeM1 = (float) (imgsize - 1);

	// Allocate the lookup_image and lookup_texture.
	if (lookup_table == NULL)
		lookup_table = new unsigned char[imgsize*imgsize*3];
	if (lookup_texture == NULL)
		lookup_texture = new tex_object_2D;

	// Fill it up.
#ifdef _DEBUG_DUMP_LOOKUP_TEXTURE
	// Debug flag to dump out the texture to file.
	// Define this, if you want to write the lookup table
	// out as a file texture.
	static boolean firstTime = true;
#else
	static boolean firstTime = false;
#endif
	if (firstTime)
	{
		MImage image;

		image.create(lookup_texture_size, lookup_texture_size);
		unsigned char *ip  = image.pixels();

		float		diff, spec, phong;
		float		lt, vt;
		float		h;
		int			i, j;
		float		invRough = 1.0 / t_roughness;

		for (i=0; i < imgsize; i++)
		{
			// Compute sqrt( 1- (L dot T)*(L dot T) )
			lt = 1.0 - 2.0 * (float)i/(float)(imgsize-1);
			diff= sqrt( 1.0-lt*lt );

			for(j=0; j < imgsize; j++)
			{
				vt= 2.0 * (float)j/(float)(imgsize-1) - 1.0;
				spec= diff*sqrt( 1-vt*vt ) - lt*vt;

				// Compute standard phong model....
				// phong= Kd*diff + Ks*powf( spec< 0.0 ? 0.0 : spec, 1.0/roughness );
				phong= t_ks*powf( spec < 0.0 ? 0.0 : spec, invRough );
				float Kddiff = t_kd*diff;
      
				h= (Kddiff+phong)*255.0 + 0.5;
				*ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
				h= (Kddiff+phong)*255.0 + 0.5;
				*ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
				h= (Kddiff+phong)*255.0 + 0.5;
				*ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
				*ip++ = 1.0;
			}
		}
		image.writeToFile("c:/aniso.iff");
		firstTime = false;
	}

	unsigned char *ip  = lookup_table;

	float		diff, spec, phong;
	float		lt, vt;
	float		h;
	int			i, j;
	float		invRough = 1.0 / t_roughness;

	for (i=0; i < imgsize; i++)
	{
		// Compute sqrt( 1- (L dot T)*(L dot T) )
		lt = 1.0 - 2.0 * (float)i/(float)(imgsize-1);
		diff= sqrt( 1.0-lt*lt );

		for(j=0; j < imgsize; j++)
		{
			vt= 2.0 * (float)j/(float)(imgsize-1) - 1.0;
			spec= diff*sqrt( 1-vt*vt ) - lt*vt;

			// Compute standard phong model....
			// phong= Kd*diff + Ks*powf( spec< 0.0 ? 0.0 : spec, 1.0/roughness );
			phong= t_ks*powf( spec < 0.0 ? 0.0 : spec, invRough );
			float Kddiff = t_kd*diff;
  
			h= (Kddiff+phong)*255.0 + 0.5;
			*ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
			h= (Kddiff+phong)*255.0 + 0.5;
			*ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
			h= (Kddiff+phong)*255.0 + 0.5;
			*ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
		}
	}
	
	fLookupTextureReprocessed = true;

	return;
}

void hwAnisotropicShader_NV20::bind_lookup_table()
{
	// Update the material table if needed
	make_lookup_texture();

	// Bind the lookup table texture
	lookup_texture->bind();

	if (fLookupTextureReprocessed)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, lookup_texture_size, lookup_texture_size, 
			0, GL_RGB, GL_UNSIGNED_BYTE, lookup_table); 
		fLookupTextureReprocessed = false;
	}

	lookup_texture->parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	lookup_texture->parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	lookup_texture->parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	lookup_texture->parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

// Initialize the necessary OpenGL extensions
//
void hwAnisotropicShader_NV20::init_ext(const char * ext)
{
	if(!glh_init_extension(ext))
	{ cerr << "Failed to initialize " << ext << "!" << endl; exit(0); }
}

hwAnisotropicShader_NV20::hwAnisotropicShader_NV20()
{
	// Get an reference to the singleton texture cache.
	m_pTextureCache = MTextureCache::instance();

	init_ext("GL_ARB_multitexture");
	//init_ext("GL_NV_register_combiners");
	init_ext("GL_NV_vertex_program");

	isDirectionalLight = true;	// light's rotation is connected to the lightRotation attr
	isNonAmbientLight = false; // Does not have decay

	// Set the lookup values to absurd values, so that the
	// look-up table automatically get recomputed during the first update.
	currentColor[0] = currentColor[1] = currentColor[2] = -1.0;
	currentRoughness = -1.0;
	currentKd = -1.0;
	currentKs = -1.0;

	lookup_texture = NULL;
	lookup_table   = NULL;
	fLookupTextureReprocessed = false;

	// Initialize callbacks.
	fBeforeNewCB = 0;
	fBeforeOpenCB = 0;
	fBeforeRemoveReferenceCB = 0;
	fMayaExitingCB = 0;
	attachSceneCallbacks();

	// Initialize the vertex program ids...
	fVertexProgramsLoaded = false;
	fVertexProgramDirectional = 0;
	fVertexProgramPointDecay = 0;
	fVertexProgramPointNoDecay = 0;
	// All vertex programs will get allocated and loaded
	// during the first refresh.
}

hwAnisotropicShader_NV20::~hwAnisotropicShader_NV20()
{
	detachSceneCallbacks();
}


void releaseVertexProgram(GLuint* pVertexProgramId)
{
	// If the vertex program id is set...
	if (*pVertexProgramId > 0)
	{
		// Unbind any vertex program...
		glBindProgramNV(GL_VERTEX_PROGRAM_NV, 0);

		glDeleteProgramsNV(1, pVertexProgramId);

		// For sanity, set the id to 0.
		*pVertexProgramId = 0;
	}
}

void hwAnisotropicShader_NV20::releaseEverything()
{
	release_lookup_texture();

	// Release all loaded vertex programs.
	if (fVertexProgramsLoaded)
	{
		releaseVertexProgram(&fVertexProgramDirectional);
		releaseVertexProgram(&fVertexProgramPointDecay);
		releaseVertexProgram(&fVertexProgramPointNoDecay);
		fVertexProgramsLoaded = false;
	}

	// Release the texture cache through refcounting.
	m_pTextureCache->release();
	if(!MTextureCache::getReferenceCount())
	{
		m_pTextureCache = 0;
	}
}

void hwAnisotropicShader_NV20::attachSceneCallbacks()
{
	fBeforeNewCB  = MSceneMessage::addCallback(MSceneMessage::kBeforeNew,  releaseCallback, this);
	fBeforeOpenCB = MSceneMessage::addCallback(MSceneMessage::kBeforeOpen, releaseCallback, this);
	fBeforeRemoveReferenceCB = MSceneMessage::addCallback(MSceneMessage::kBeforeRemoveReference, 
														  releaseCallback, this);
	fMayaExitingCB = MSceneMessage::addCallback(MSceneMessage::kMayaExiting, releaseCallback, this);
}

/*static*/
void hwAnisotropicShader_NV20::releaseCallback(void* clientData)
{
	hwAnisotropicShader_NV20 *pThis = (hwAnisotropicShader_NV20*) clientData;
	pThis->releaseEverything();
}

void hwAnisotropicShader_NV20::detachSceneCallbacks()
{
	if (fBeforeNewCB)
		MMessage::removeCallback(fBeforeNewCB);
	if (fBeforeOpenCB)
		MMessage::removeCallback(fBeforeOpenCB);
	if (fBeforeRemoveReferenceCB)
		MMessage::removeCallback(fBeforeRemoveReferenceCB);
	if (fMayaExitingCB)
		MMessage::removeCallback(fMayaExitingCB);

	fBeforeNewCB = 0;
	fBeforeOpenCB = 0;
	fBeforeRemoveReferenceCB = 0;
	fMayaExitingCB = 0;
}

MStatus initializePlugin( MObject obj )
{ 
	MStatus   status;
	
	const MString UserClassify( "shader/surface/utility" );

	MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.5", "Any");
	status = plugin.registerNode( "hwAnisotropicShader_NV20", hwAnisotropicShader_NV20::id, 
			                      hwAnisotropicShader_NV20::creator, hwAnisotropicShader_NV20::initialize,
								  MPxNode::kHwShaderNode, &UserClassify );
	if (!status) {
		status.perror("registerNode");
		return status;
	}

	return MS::kSuccess;
}

MStatus uninitializePlugin( MObject obj )
{
	MStatus   status;
	
	MFnPlugin plugin( obj );

	plugin.deregisterNode( hwAnisotropicShader_NV20::id );
	if (!status) {
		status.perror("deregisterNode");
		return status;
	}

	return MS::kSuccess;
}


void * hwAnisotropicShader_NV20::creator()
{
    return new hwAnisotropicShader_NV20();
}

// Initialize the plug-in. Called once when the plug-in is loaded.
// This mostly involve creating attributes.
MStatus hwAnisotropicShader_NV20::initialize()
{
    MFnNumericAttribute nAttr; 
	MStatus status;
	MFnTypedAttribute sAttr; // For string attributes

	// Material color 
    colorR = nAttr.create( "colorR", "cr",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    colorG = nAttr.create( "colorG", "cg",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    colorB = nAttr.create( "colorB", "cb",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    color = nAttr.create( "color", "c", colorR, colorG, colorB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f, 0.5f, 0.5f);
    nAttr.setUsedAsColor(true);

	// Material parameters. Set defaults to be "shiny"
	//
    kDiffuse = nAttr.create( "kDiffuse", "kd", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.2f);

    kSpecular = nAttr.create( "kSpecular", "ks", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.9f);

    roughness = nAttr.create( "roughness", "rn", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.025f);
	
	// Add the attributes here
    addAttribute(color);
    addAttribute(roughness);
	addAttribute(kDiffuse);
    addAttribute(kSpecular);
    attributeAffects (colorR, outColor);
    attributeAffects (colorG, outColor);
    attributeAffects (colorB, outColor);
    attributeAffects (color,  outColor);
    attributeAffects (roughness, outColor);
    attributeAffects (kDiffuse, outColor);
    attributeAffects (kSpecular, outColor);

    return MS::kSuccess;
}


// To get 3 float values from the node attribute
//
MStatus hwAnisotropicShader_NV20::getFloat3(MObject attr, float value[3])
{
	MStatus status = MS::kSuccess;

	// Get the attr to use
	//
	MPlug	plug(thisMObject(), attr);

	MObject object;
	status = plug.getValue(object);
	if (!status)
	{
		status.perror("hwAnisotropicShader_NV20::getFloat3 plug.getValue.");
		return status;
	}

	MFnNumericData data(object, &status);
	if (!status)
	{
		status.perror("hwAnisotropicShader_NV20::getFloat3 construct data.");
		return status;
	}

	status = data.getData(value[0], value[1], value[2]);
	if (!status)
	{
		status.perror("hwAnisotropicShader_NV20::getFloat3 get values.");
		return status;
	}

	return status;
}

// To get a string value from the node attribute
//
MStatus hwAnisotropicShader_NV20::getString(MObject attr, MString &str)
{
	MPlug	plug(thisMObject(), attr);
	MStatus status = plug.getValue( str );

	return status;
}

/* virtual */
MStatus	hwAnisotropicShader_NV20::bind(const MDrawRequest& request, M3dView& view)
{
	MStatus status;

	// Get the material color
	float t_color[3];
	getFloat3(color, t_color);

	view.beginGL();
	{
		glPushAttrib( GL_ALL_ATTRIB_BITS );
		glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

		// Set the material color
		glColor4f(t_color[0], t_color[1], t_color[2], 1.0f);

		// Bind anisotropic texture in texture unit 1
		glActiveTextureARB( GL_TEXTURE0_ARB );
		glEnable(GL_TEXTURE_2D);
		bind_lookup_table();	
		
	}
	view.endGL();

	return MS::kSuccess;
}


/* virtual */
MStatus	hwAnisotropicShader_NV20::unbind(const MDrawRequest& request,
			   M3dView& view)
{
	view.beginGL();
	glActiveTextureARB( GL_TEXTURE0_ARB );
	glDisable(GL_TEXTURE_2D);

	glPopClientAttrib();
	glPopAttrib();

	view.endGL();

	return MS::kSuccess;
}

/* virtual */
MStatus	hwAnisotropicShader_NV20::geometry( const MDrawRequest& request,
								M3dView& view,
							    int prim,
								unsigned int writable,
								int indexCount,
								const unsigned int * indexArray,
								int vertexCount,
								const int * vertexIDs,
								const float * vertexArray,
								int normalCount,
								const float ** normalArrays,
								int colorCount,
								const float ** colorArrays,
								int texCoordCount,
								const float ** texCoordArrays)
{
	// We assume triangles here.
	//
	if (prim != GL_TRIANGLES)
		return MS::kSuccess;		// [claforte August 9th 2001] Should this be MS::kFailure?

	view.beginGL();

	// Find out if we have a directional light before
	// loading the vertex program since we use a different
	// vertex program depending on whether the light is a directional
	// one or not
	//
	isDirectionalLight = true; // Assume is directional
	isNonAmbientLight = false;

	boolean useDefaultLight = false;

	unsigned int numLights;
	MDagPath lightPath;
	view.getLightCount( numLights );
	if (numLights)
	{
		M3dView::LightingMode mode;
		view.getLightingMode(mode);
		if (mode == M3dView::kLightDefault)
		{
			useDefaultLight = true;
			isDirectionalLight = true;
		}
		else
		{
			view.getLightPath( 0, lightPath );
			MObject lightObj = lightPath.node();

			isDirectionalLight = lightObj.hasFn( MFn::kDirectionalLight );
			isNonAmbientLight = lightObj.hasFn( MFn::kNonAmbientLight );
			if (isNonAmbientLight)
			{
				MFnNonAmbientLight mNonAmbientLight(lightObj);
				if (mNonAmbientLight.decayRate() == 0)
					isNonAmbientLight = false;
			}
		}
	}

	// Load the appropriate vertex program
	loadVertexProgramGL();

	// Bind and enable the appropriate vertex program,
	// depending on light type.
	//
	if (isDirectionalLight)
		glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramDirectional);
	else if (isNonAmbientLight)
		glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramPointDecay);
	else
		glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramPointNoDecay);

	// Assert if an error occurs after binding the vertex programs.
	GLenum error = glGetError();
	assert(error == GL_NO_ERROR);

	// Enable the vertex program.
	glEnable(GL_VERTEX_PROGRAM_NV);

	// Get object's inverse matrix (ie: from world to object space.)
	MDagPath objPath = request.multiPath();
	MMatrix objMatrix = objPath.inclusiveMatrixInverse();

	// Get the light direction in object space.
	// This code assumes that there is a directional light in the scene,
	// and that it is the first light in DAG order.
	//
	if (numLights)
	{
		// Handle default lighting mode
		if (useDefaultLight )
		{
			glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, 0, 0, 1, 1); // Light dir = 0,0,1
			glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1.0, 1.0, 1.0, 1); // Light color = 1,1,1,1
		}

		// Handle other lighting modes
		else
		{
			view.getLightPath( 0, lightPath );
			MMatrix matrix = lightPath.inclusiveMatrix();

			isDirectionalLight = lightPath.node().hasFn( MFn::kDirectionalLight );

			// Get rotation of a directional light in object space
			if (isDirectionalLight)
			{
				MVector lightDir(0,0,1); 
				lightDir *= matrix;

				// Transform into object space
				lightDir *= objMatrix;

				// Provide the direction to the vertex program (constant 11)
				// Need to send it over negated since Maya reverses light direction !!!
				glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, -lightDir.x, -lightDir.y, -lightDir.z, 1);
			}
			// Get the position of a non-directional light in object space
			else
			{
				MPoint lightPos(0,0,0); // origin
				lightPos *= matrix;

				// Transform into object space
				lightPos *= objMatrix;

				// Provide the position to the vertex program (constant 11)
				// Need to send it over negated since Maya reverses light positions !!!
				glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, -lightPos.x, -lightPos.y, -lightPos.z, 1);
			}

			MFnLight mLight(lightPath.node());

			// Set the light's color.
			MColor lightColor = mLight.color();
			float intensity = mLight.intensity();
			lightColor.r *= intensity;
			lightColor.g *= intensity;
			lightColor.b *= intensity;
			// This should NOT be clamped here, so the vertex program
			// does the correct computation ! Don't uncomment these
			// lines.
			//if (lightColor.r > 1.0) lightColor.r = 1.0;
			//if (lightColor.g > 1.0) lightColor.g = 1.0;
			//if (lightColor.b > 1.0) lightColor.b = 1.0;
			glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, lightColor.r, lightColor.g, lightColor.b, 1);
		}
	}
	else
	{
		// Set some default values
		glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, 0, 0, 1, 1);
		glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1.0, 1.0, 1.0, 1);
	}

	// Find the camera position in geometry's object space
	float cameraPos[4] = {0.0f, 0.0f, 0.0f, 0.0f};
	{
		MDagPath camDag;
		view.getCamera(camDag);

		MPoint cameraInObject(0,0,0); 
		
		MMatrix cameraToWorldMatrix = camDag.inclusiveMatrix();

		cameraInObject *= cameraToWorldMatrix; // to world
		cameraInObject *= objMatrix;

		glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 12, cameraInObject.x, cameraInObject.y, cameraInObject.z, 1);
	}

	// VERTEX CONSTANTS:
	//  c0- 3  4x4 ModelView-Projection composite matrix
	//  c4	  (1/2, 1, 0, 0) vector needed to compute (u,v)
	//
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 4, 0.5, 1.0, 0.0, 0.0);

	// VERTEX REGISTERS (Attributes):
	// 0 - coord
	// 2 - tangent
	glVertexAttribPointerNV( 0, 3, GL_FLOAT, 0, vertexArray );
	glVertexAttribPointerNV( 2, 3, GL_FLOAT, 0, normalArrays[TANGENT_INDEX] ); 

	glEnableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
	glEnableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );

	glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indexArray);

	glDisableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
	glDisableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );
	
	glDisable(GL_VERTEX_PROGRAM_NV);

	view.endGL();

	return MS::kSuccess;
}

/* virtual */
int		hwAnisotropicShader_NV20::normalsPerVertex()
{
	// Need the normal and tangents for this computation
	return 2;
}

/* virtual */
int		hwAnisotropicShader_NV20::texCoordsPerVertex()
{
	return 1;
}

// Release the lookup texture/image.
void hwAnisotropicShader_NV20::release_lookup_texture()
{
	if (lookup_table)
	{
		delete lookup_table;
		lookup_table = NULL;
	}
	
	if (lookup_texture)
	{
		delete lookup_texture;
		lookup_table = NULL;
	}
}
